home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Source / WAIS / next-ui / WAISControl.m < prev    next >
Encoding:
Text File  |  1992-07-16  |  21.2 KB  |  722 lines

  1. // WAISControl.m
  2. //
  3. // Free software created 1 Feb 1992
  4. // by Paul Burchard <burchard@math.utah.edu>.
  5.  
  6. #import "WAISControl.h"
  7. #import "Wais.h"
  8. #import "QuestionDoc.h"
  9. #import "SourceDoc.h"
  10. #import "BrowserPane.h"
  11. #import "IconWellControl.h"
  12. #import "MailSpeaker.h"
  13. #import <appkit/appkit.h>
  14. #import <sys/file.h>
  15.  
  16.  
  17. @implementation WAISControl
  18.  
  19.  
  20. // ----------------------- PREFERENCES ------------------------ //
  21.  
  22.  
  23. static NXDefaultsVector WAIStationDefaults =
  24. {
  25.     { "SystemFolder", "/LocalLibrary/WAIS" },
  26.     { "UserFolder", "~/Library/WAIS" },
  27.     { "OpenOnRetrieval", "YES" },
  28.     { "MaxSearchResults", "30" },
  29.     { "MailListAddress", "listserv@think.com" },
  30.     { "MailListMessage", "ADD wais-talk\nADD wais-interest\nADD wais-discussion\n" },
  31.     { "BugReportAddress", "burchard@math.utah.edu" },
  32.     { "BugReportCC", "bug-wais@think.com" },
  33.     { "BugReportSubject", "Comments on WAIStation.app " WAISTATION_VERSION " for NeXT" },
  34.     { "BugReportMessage", "Please help us in making the WAIS software better.  
  35. Please answer candidly.
  36.  
  37. How was the installation on the NeXT?
  38. How is the documentation?
  39. Comments on the server code:
  40. Comments on the NeXT interface:
  41. Comments on the existing WAIS sources on the net:
  42. What WAIS sources would you like to see:
  43. Would you like to help?  In what way?
  44. General comments:
  45. " },
  46.     { NULL }
  47. };
  48.  
  49. + initialize
  50. {
  51.     NXRegisterDefaults("WAIStation", WAIStationDefaults);
  52.     return self;
  53. }
  54.  
  55. // Returns NXAtom-ized version of full folder name.
  56. // Adds this name to the search path for the WaisClass (if not already there).
  57. // Name will be inserted at beginning if where<0, at end if where>0.
  58. // Note: subname may be NULL.
  59. - (NXAtom)addToPath:WaisClass inPos:(int)where
  60.     subdir:(const char *)subname ofFolder:(const char *)name
  61. {
  62.     char *buf;
  63.     const char *home;
  64.     NXAtom rtn;
  65.     int i, len, cnt;
  66.     id flist;
  67.     
  68.     // Valid folder?
  69.     if(!name) return 0;
  70.     if(!(name[0]=='/' || (name[0]=='~' && name[1]=='/'))) return 0;
  71.     
  72.     // Create buffer...
  73.     home = NXHomeDirectory();
  74.     len = 1 + strlen(name) + strlen("/");
  75.     if(home) len += strlen(home);
  76.     if(subname) len += strlen(subname);
  77.     buf = s_malloc(len);
  78.     if(!buf) return 0;
  79.     
  80.     // Replace initial '~' with home directory.
  81.     if(name[0] == '~')
  82.     {
  83.         if(home) strcpy(buf, home);
  84.     strcat(buf, name+1);
  85.     }
  86.     else strcpy(buf, name);
  87.     len = strlen(buf);
  88.     
  89.     // Append subfolder if any (fix trailing '/').
  90.     if(subname)
  91.     {
  92.         if(buf[len-1] != '/') strcat(buf, "/");
  93.     strcat(buf, subname);
  94.     }
  95.     else if(buf[len-1] == '/') buf[len-1] = 0;
  96.  
  97.     // Append atomized version to WaisClass's path and return it.
  98.     rtn = NXUniqueString(buf); s_free(buf);
  99.     flist = [WaisClass folderList]; cnt = [flist count];
  100.     for(i=0; i<cnt; i++) if(rtn == *((NXAtom *)[flist elementAt:i])) break;
  101.     if(i >= cnt)
  102.     {
  103.         if(where > 0) [flist addElement:(void *)&rtn];
  104.     else [flist insert:(void *)&rtn at:0];
  105.     }
  106.     return rtn;
  107. }
  108.  
  109. - createFolder:(const char *)name onError:(const char *)alertMsg
  110. {
  111.     char cmd[2*MAXPATHLEN];
  112.     
  113.     [Wais lockFileIO];
  114.     if(!name)
  115.     {
  116.     if(stringTable) NXRunAlertPanel(
  117.         [stringTable valueForStringKey:"WAIStation"],
  118.         [stringTable valueForStringKey:alertMsg],
  119.         [stringTable valueForStringKey:"OK"], NULL, NULL);
  120.     else { fprintf(stderr, "%s\n", alertMsg); fflush(stderr); }
  121.     [Wais unlockFileIO];
  122.     return nil;
  123.     }
  124.     if(0 == access(name, R_OK|W_OK|X_OK))
  125.         { [Wais unlockFileIO]; return self; }
  126.     sprintf(cmd, "mkdirs \'%s\'", name);
  127.     system(cmd);
  128.     if(0 == access(name, R_OK|W_OK|X_OK))
  129.         { [Wais unlockFileIO]; return self; }
  130.     if(stringTable) NXRunAlertPanel(
  131.         [stringTable valueForStringKey:"WAIStation"],
  132.     [stringTable valueForStringKey:alertMsg],
  133.     [stringTable valueForStringKey:"OK"], NULL, NULL);
  134.     else { fprintf(stderr, "%s\n", alertMsg); fflush(stderr); }
  135.     [Wais unlockFileIO];
  136.     return nil;
  137. }
  138.  
  139. - usePrefs
  140. {
  141.     const char *basefolder, *number;
  142.     NXAtom folder;
  143.     int limit;
  144.     
  145.     // Set flag to open or not open docs as they are retrieved.
  146.     if(!NXGetDefaultValue("WAIStation", "OpenOnRetrieval")
  147.         || 'Y'==*NXGetDefaultValue("WAIStation", "OpenOnRetrieval")
  148.         || 'y'==*NXGetDefaultValue("WAIStation", "OpenOnRetrieval"))
  149.     isOpenOnRetrieval = YES;
  150.     else isOpenOnRetrieval = NO;
  151.     
  152.     // Set class-wide search limit for questions.
  153.     if((number=NXGetDefaultValue("WAIStation", "MaxSearchResults"))
  154.         && 1==sscanf(number, " %d ", &limit))
  155.     [WaisQuestion setSearchLimit:limit];
  156.     
  157.     // Create user directories if necessary and prepend to appropriate paths.
  158.     if(NXGetDefaultValue("WAIStation", "UserFolder"))
  159.         basefolder = NXGetDefaultValue("WAIStation", "UserFolder");
  160.     else basefolder = "~/Library/WAIS";
  161.  
  162.     folder = [self addToPath:[WaisSource class] inPos:(-1)
  163.         subdir:"sources" ofFolder:basefolder];
  164.     [self createFolder:folder
  165.         onError:"Can't create folder for WAIS sources"];
  166.     
  167.     folder = [self addToPath:[WaisDocument class] inPos:(-1)
  168.         subdir:"documents" ofFolder:basefolder];
  169.     [self createFolder:folder
  170.         onError:"Can't create folder for WAIS documents"];
  171.     
  172.     folder = [self addToPath:[WaisQuestion class] inPos:(-1)
  173.         subdir:"questions" ofFolder:basefolder];
  174.     [self createFolder:folder
  175.         onError:"Can't create folder for WAIS questions"];
  176.     
  177.     // Append system directories (if any) to appropriate paths.
  178.     if(NXGetDefaultValue("WAIStation", "SystemFolder"))
  179.     {
  180.         basefolder = NXGetDefaultValue("WAIStation", "SystemFolder");
  181.  
  182.     [self addToPath:[WaisSource class] inPos:1
  183.         subdir:"sources" ofFolder:basefolder];
  184.     [self addToPath:[WaisSource class] inPos:1
  185.         subdir:"wais-sources" ofFolder:basefolder];
  186.     [self addToPath:[WaisDocument class] inPos:1
  187.         subdir:"documents" ofFolder:basefolder];
  188.     [self addToPath:[WaisQuestion class] inPos:1
  189.         subdir:"questions" ofFolder:basefolder];
  190.     }
  191.     return self;
  192. }
  193.  
  194. - updatePrefs:sender
  195. {
  196.     char number[128];
  197.     
  198.     // Get new prefs from panel and save in database.
  199.     if([prefsIsOpenOnRetrieval state] != 0)
  200.         NXWriteDefault("WAIStation", "OpenOnRetrieval", "YES");
  201.     else NXWriteDefault("WAIStation", "OpenOnRetrieval", "NO");
  202.     sprintf(number, "%d", [prefsSearchLimit intValue]);
  203.     NXWriteDefault("WAIStation", "MaxSearchResults", number);
  204.     if([prefsSystemFolder stringValue])
  205.         NXWriteDefault("WAIStation",
  206.         "SystemFolder", [prefsSystemFolder stringValue]);
  207.     if([prefsUserFolder stringValue])
  208.         NXWriteDefault("WAIStation",
  209.         "UserFolder", [prefsUserFolder stringValue]);
  210.     [self usePrefs];
  211.     return self;
  212. }
  213.  
  214. - prefs:sender
  215. {
  216.     const char *number;
  217.     int limit;
  218.     
  219.     if(!prefsPanel) [NXApp loadNibSection:"Preferences.nib" owner:self];
  220.     if(!NXGetDefaultValue("WAIStation", "OpenOnRetrieval")
  221.         || 'Y'==*NXGetDefaultValue("WAIStation", "OpenOnRetrieval")
  222.         || 'y'==*NXGetDefaultValue("WAIStation", "OpenOnRetrieval"))
  223.     [prefsIsOpenOnRetrieval setState:1];
  224.     else [prefsIsOpenOnRetrieval setState:0];
  225.     
  226.     if((number=NXGetDefaultValue("WAIStation", "MaxSearchResults"))
  227.         && 1==sscanf(number, " %d ", &limit))
  228.     [prefsSearchLimit setIntValue:limit];
  229.     [prefsSearchLimit sendAction:[prefsSearchLimit action]
  230.         to:[prefsSearchLimit target]];
  231.     
  232.     [prefsSystemFolder
  233.         setStringValue:NXGetDefaultValue("WAIStation", "SystemFolder")];
  234.     [prefsUserFolder
  235.         setStringValue:NXGetDefaultValue("WAIStation", "UserFolder")];
  236.  
  237.     [prefsPanel update];
  238.     [prefsPanel makeKeyAndOrderFront:self];
  239.     
  240.     // Set up controller for icon wells; transfer info from TextFields.
  241.     if(!prefsIWC) prefsIWC =
  242.         [[IconWellControl alloc] initWindow:prefsPanel];
  243.     [prefsSystemFolder sendAction:[prefsSystemFolder action]
  244.         to:[prefsSystemFolder target]];
  245.     [prefsUserFolder sendAction:[prefsUserFolder action]
  246.         to:[prefsUserFolder target]];
  247.     return self;
  248. }
  249.  
  250. + (const char *)defaultFolder
  251. {
  252.     if(NXGetDefaultValue("WAIStation", "UserFolder"))
  253.     return NXGetDefaultValue("WAIStation", "UserFolder");
  254.     else return [[Wais folderList] elementAt:0];
  255. }
  256.  
  257. // ----------------------- RETRIEVAL ------------------------ //
  258.  
  259.  
  260. // Callback from retrieval thread to main thread.
  261. - retrievalDone:data
  262. {
  263.     int docAt, ok;
  264.     id doc, check_rtn;
  265.  
  266.     if(!data || ![data isKindOf:[List class]]
  267.         || ![[data objectAt:0] isKindOf:[Wais class]])
  268.     return nil;
  269.     doc = [data objectAt:0];
  270.     check_rtn = [data objectAt:1];
  271.     [data free];
  272.     
  273.     // If successfully retrieved and user desires docs to be opened on
  274.     // retrieval, pop open doc.
  275.     if(check_rtn && isOpenOnRetrieval) [self openFile:[doc key] ok:&ok];
  276.  
  277.     // Enable browser entry for doc if check_rtn non-nil, else remove it.
  278.     if(documentPaletteBrowser
  279.     && (docAt=[documentPaletteBrowser indexOfEntry:[doc key]])>=0)
  280.     {
  281.     if(check_rtn) [documentPaletteBrowser setEntryEnabled:YES at:docAt];
  282.     else [documentPaletteBrowser removeEntryAt:docAt];
  283.     }
  284.     else if(sourcePaletteBrowser
  285.     && (docAt=[sourcePaletteBrowser indexOfEntry:[doc key]])>=0)
  286.     {
  287.     if(check_rtn) [sourcePaletteBrowser setEntryEnabled:YES at:docAt];
  288.     else [sourcePaletteBrowser removeEntryAt:docAt];
  289.     }
  290.     return self;
  291. }
  292.  
  293. any_t retrieval_thread(any_t rawArgs)
  294. {
  295.     retrieval_args args = (retrieval_args)rawArgs;
  296.     id doc, src, check_rtn, data;
  297.     
  298.     while(YES)
  299.     {
  300.     // Wait for retrieval requests to show up in list.
  301.     mutex_lock(args->requestMutex);
  302.     while([args->docList count] <= 0)
  303.         condition_wait(args->requestCondition, args->requestMutex);
  304.     doc = [args->docList objectAt:0];
  305.     mutex_unlock(args->requestMutex);
  306.     if(!doc) continue;
  307.  
  308.     // Only one retrieval at a time, since same port may be used.
  309.     // If doc file describes a source, load it into Wais system.
  310.     // Else write auxiliary WAIS file to preserve access info.
  311.     check_rtn = [doc retrieve];
  312.     if(check_rtn)
  313.     {
  314.         if([doc valueForStringKey:":type"]
  315.             && 0==strcmp([doc valueForStringKey:":type"], "WSRC"))
  316.         {
  317.             src = [[WaisSource alloc] initKey:[doc key]];
  318.         [src readWaisFile];
  319.         }
  320.         else [doc writeWaisFile];
  321.     }
  322.  
  323.     // Report back:
  324.     // 1. Pop open retrieved doc if necessary.
  325.     // 2. Enable browser entry if successful, else remove it.
  326.     // (Use callback to main thread since AppKit is not thread-proof.
  327.     //    Callback will free data list.)
  328.     // (Note: [NXApp delegate] = us.)
  329.     data = [[List alloc] init];
  330.     [data addObject:doc]; [data addObject:check_rtn];
  331.     [Wais callback:[NXApp delegate]
  332.         perform:@selector(retrievalDone:) with:data];
  333.  
  334.     // Remove entry from request list.
  335.     mutex_lock(args->requestMutex);
  336.     [args->docList removeObjectAt:0];
  337.     mutex_unlock(args->requestMutex);
  338.     }
  339.     // Never reaches this.
  340.     return 0;
  341. }
  342.  
  343. - restartThread
  344. {
  345.     // Already running?
  346.     if(retrievalThread) return nil;
  347.     
  348.     // Create and detach thread.
  349.     retrievalArgs.docList = retrievalList;
  350.     retrievalArgs.requestMutex = requestMutex = mutex_alloc();
  351.     retrievalArgs.requestCondition = requestCondition = condition_alloc();
  352.     retrievalThread = cthread_fork(retrieval_thread, (any_t)&retrievalArgs);
  353.     if(!retrievalThread) return nil;
  354.     cthread_detach(retrievalThread);
  355.     return self;
  356. }
  357.  
  358. - terminateThread
  359. {
  360.     int i;
  361.     
  362.     // Try to abort the retrieval thread.
  363.     //!!! Zapping threads may screw up the mutex locking, so we don't
  364.     //!!! try to recycle old mutexes and conditions.  Even if old thread stays
  365.     //!!! around, it's probably not doing much, so its freedom from the
  366.     //!!! new mutexes shouldn't be a problem.
  367.     if(!retrievalThread) return self;
  368.     thread_suspend(cthread_thread(retrievalThread));
  369.     cthread_abort(retrievalThread);
  370.     retrievalThread = 0;
  371.     requestMutex = 0;
  372.     requestCondition = 0;
  373.     [Wais waisNewLocks];
  374.     
  375.     // Clear retrieval request list.
  376.     // Update palettes (removing disabled entries).
  377.     [retrievalList empty];
  378.     for(i=[documentPaletteBrowser count]-1; i>=0; i--)
  379.         if(![documentPaletteBrowser isEntryEnabledAt:i])
  380.         [documentPaletteBrowser removeEntryAt:i];
  381.     [documentPaletteBrowser update];
  382.     for(i=[sourcePaletteBrowser count]-1; i>=0; i--)
  383.         if(![sourcePaletteBrowser isEntryEnabledAt:i])
  384.         [sourcePaletteBrowser removeEntryAt:i];
  385.     [sourcePaletteBrowser update];
  386.     return self;
  387. }
  388.  
  389. - cancelRetrievals:sender
  390. {
  391.     [self terminateThread];
  392.     [self restartThread];
  393.     return self;
  394. }
  395.  
  396. //!!! This method should be called before doing any operation that might
  397. //!!! access a WaisDocument.  If it is being retrieved, you should
  398. //!!! not access it as that might collide with operations in the
  399. //!!! retrieval thread.
  400. - (BOOL)isDocumentBeingRetrieved:waisDoc
  401. {
  402.     int docAt;
  403.  
  404.     if(requestMutex) mutex_lock(requestMutex);
  405.     docAt = [retrievalList indexOf:waisDoc];
  406.     if(requestMutex) mutex_unlock(requestMutex);
  407.     if(docAt!=NX_NOT_IN_LIST && docAt>=0) return YES;
  408.     return NO;
  409. }
  410.  
  411. - retrieveDocuments:(const char *)keyList
  412. {
  413.     int docAt;
  414.     char *docKeys, *thisKey, *nextKey;
  415.     id doc, inBrowser;
  416.     BOOL isSource;
  417.     
  418.     // Assumes keyList is TAB-separated list of keys.
  419.     if(!keyList || !retrievalThread) return nil;
  420.     if(!(docKeys = s_malloc(strlen(keyList) + 1))) return nil;
  421.     strcpy(docKeys, keyList);
  422.     for(thisKey=docKeys, nextKey=strchr(thisKey, '\t');
  423.         thisKey; thisKey=nextKey, nextKey=strchr(thisKey?thisKey:"", '\t'))
  424.     {
  425.         if(nextKey) *nextKey++ = 0;
  426.     
  427.     // Check if doc is aleady retrieved or being retrieved (then ignore).
  428.     if(!(doc = [WaisDocument objectForKey:thisKey])) continue;
  429.     if([self isDocumentBeingRetrieved:doc] || [doc isRetrieved])
  430.         continue;
  431.  
  432.     // Check if it is really a source.
  433.     isSource = NO;
  434.     inBrowser = documentPaletteBrowser;
  435.     if([doc valueForStringKey:":type"]
  436.         && 0==strcmp([doc valueForStringKey:":type"], "WSRC"))
  437.         isSource = YES;
  438.     if(isSource) inBrowser = sourcePaletteBrowser;
  439.     
  440.     // Enter into correct palette browser, but disabled.
  441.     if(inBrowser)
  442.     {
  443.         docAt = [inBrowser indexAddEntry:[doc key]];
  444.         [inBrowser setEntryEnabled:NO at:docAt];
  445.         [inBrowser update];
  446.     }
  447.     
  448.     // Enter doc into queue and notify thead of retrieval request.
  449.     if(requestMutex) mutex_lock(requestMutex);
  450.     [retrievalList addObjectIfAbsent:doc];
  451.     if(requestMutex) mutex_unlock(requestMutex);
  452.     if(requestCondition) condition_signal(requestCondition);
  453.     }
  454.     s_free(docKeys);
  455.     return self;
  456. }
  457.  
  458. - retrieveDocumentsFrom:sender
  459. {
  460.     return [self retrieveDocuments:[sender stringValue]];
  461. }
  462.  
  463.  
  464. // ----------------------- PANEL SETUP ----------------------- //
  465.  
  466.  
  467. - init
  468. {
  469.     id Handlers;
  470.     
  471.     [super init];
  472.  
  473.     // Classify file types.
  474.     Handlers = [[List alloc] initCount:0];
  475.     [Handlers addObject:[QuestionDoc class]];
  476.     [Handlers addObject:[SourceDoc class]];
  477.     [super setDocHandlers:Handlers];
  478.     launchWithCreateDoc = YES;
  479.  
  480.     // Keep track of retrieval threads.
  481.     retrievalList = [[List alloc] initCount:0];
  482.     retrievalThread = 0;
  483.     return self;
  484. }
  485.  
  486. - free
  487. {
  488.     if(retrievalThread) [self terminateThread];
  489.     [retrievalList free];
  490.     if(requestMutex) mutex_free(requestMutex);
  491.     if(requestCondition) condition_free(requestCondition);
  492.     return [super free];
  493. }
  494.  
  495. - appDidInit:sender
  496. {
  497.     char userInfo[1024];
  498.     char hostname[512];
  499.  
  500.     [super appDidInit:sender];
  501.     
  502.     // Enable Alert Panels for Wais classes.
  503.     [Wais setStringTable:stringTable];
  504.     
  505.     // Set up according to preferences.
  506.     [self usePrefs];
  507.     
  508.     // Set user registration string.
  509.     gethostname(hostname, 512); hostname[512-1] = 0;
  510.     sprintf(userInfo, "WAIStation.app %s, from host: %s, user: %s",
  511.     WAISTATION_VERSION, hostname, WAIS_USER);
  512.     [WaisSource registerUser:userInfo];
  513.     
  514.     // Pop up source and doc palettes.
  515.     [self sourcePalette:self];
  516.     [self documentPalette:self];
  517.     [sourcePalettePanel makeKeyAndOrderFront:self];
  518.     
  519.     // Start retrieval thread.
  520.     // Must be done after doc and source palette created if we want updates!
  521.     [self restartThread];
  522.     return self;
  523. }
  524.  
  525. - help:sender
  526. {
  527.     if(!helpPanel) [NXApp loadNibSection:"Help.nib" owner:self];
  528.     [helpPanel makeKeyAndOrderFront:self];
  529.     return self;
  530. }
  531.  
  532. - info:sender
  533. {
  534.     if(!infoPanel) [NXApp loadNibSection:"Info.nib" owner:self];
  535.     [infoPanel makeKeyAndOrderFront:self];
  536.     return self;
  537. }
  538.  
  539. - bugReport:sender
  540. {
  541.     //!!! Note the "MailSendDemo" port is undocumented.
  542.     id mailSpeaker = [[MailSpeaker alloc] init]; 
  543.     port_t mailPort = NXPortFromName("MailSendDemo", NULL); 
  544.  
  545.     if(!mailSpeaker || mailPort==PORT_NULL)
  546.         { [mailSpeaker free]; return nil; }
  547.     [mailSpeaker setSendPort:mailPort];
  548.     [mailSpeaker openSend];
  549.     if(NXGetDefaultValue("WAIStation", "BugReportAddress"))
  550.     [mailSpeaker setTo:NXGetDefaultValue("WAIStation", 
  551.         "BugReportAddress")];
  552.     if(NXGetDefaultValue("WAIStation", "BugReportCC"))
  553.     [mailSpeaker setCc:NXGetDefaultValue("WAIStation", "BugReportCC")];
  554.     if(NXGetDefaultValue("WAIStation", "BugReportSubject"))
  555.     [mailSpeaker setSubject:NXGetDefaultValue("WAIStation", 
  556.         "BugReportSubject")];
  557.     if(NXGetDefaultValue("WAIStation", "BugReportMessage"))
  558.     [mailSpeaker setBody:NXGetDefaultValue("WAIStation", 
  559.         "BugReportMessage")];
  560.     [mailSpeaker free];
  561.     port_deallocate(task_self(), mailPort); 
  562.     return self;
  563. }
  564.  
  565. - signMeUp:sender
  566. {
  567.     //!!! Note the "MailSendDemo" port is undocumented.
  568.     id mailSpeaker = [[MailSpeaker alloc] init]; 
  569.     port_t mailPort = NXPortFromName("MailSendDemo", NULL); 
  570.  
  571.     if(!mailSpeaker || mailPort==PORT_NULL)
  572.         { [mailSpeaker free]; return nil; }
  573.     [mailSpeaker setSendPort:mailPort]; 
  574.     [mailSpeaker openSend];
  575.     if(NXGetDefaultValue("WAIStation", "MailListAddress"))
  576.     [mailSpeaker setTo:NXGetDefaultValue("WAIStation", "MailListAddress")];
  577.     [mailSpeaker setSubject:"SUBSCRIBE"];
  578.     if(NXGetDefaultValue("WAIStation", "MailListMessage"))
  579.     [mailSpeaker setBody:NXGetDefaultValue("WAIStation", 
  580.         "MailListMessage")];
  581.     [mailSpeaker free];
  582.     port_deallocate(task_self(), mailPort); 
  583.     return self;
  584. }
  585.  
  586. - sourcePalette:sender
  587. {
  588.     int i, j, n, m;
  589.     BOOL quiet;
  590.     id srcList, srcPath;
  591.     const char **srcFolder;
  592.     
  593.     if(!(srcPath = [WaisSource folderList])) return nil;
  594.     if(!sourcePalettePanel)
  595.     {
  596.         [NXApp loadNibSection:"SourcePalette.nib" owner:self];
  597.     [[sourcePaletteBrowser setAlphabetized:YES] setAbbreviated:YES];
  598.     [sourcePaletteBrowser setEditable:YES];
  599.     }
  600.     
  601.     // Load all directories in source path (ignore missing ones).
  602.     [sourcePaletteBrowser clear];
  603.     quiet = [Wais isQuiet];
  604.     [Wais setQuiet:YES];
  605.     m = [srcPath count];
  606.     for(j=0; j<m; j++) if((srcFolder=(const char **)[srcPath elementAt:j])
  607.         && (*srcFolder) && (srcList=[WaisSource loadFolder:*srcFolder]))
  608.     {
  609.     n = [srcList count];
  610.     for(i=0; i<n; i++)
  611.         [sourcePaletteBrowser addEntry:[[srcList objectAt:i] key]];
  612.     [srcList free];
  613.     }
  614.     [Wais setQuiet:quiet];
  615.     [sourcePaletteBrowser update];
  616.     [sourcePalettePanel orderFront:self];
  617.     
  618.     if(!sourcePaletteIWC) sourcePaletteIWC =
  619.         [[IconWellControl alloc] initWindow:sourcePalettePanel];
  620.     return self;
  621. }
  622.  
  623. - documentPalette:sender
  624. {
  625.     int i, j, n, m;
  626.     BOOL quiet;
  627.     id docList, docPath;
  628.     const char **docFolder;
  629.     
  630.     if(!(docPath = [WaisDocument folderList])) return nil;
  631.     if(!documentPalettePanel)
  632.     {
  633.         [NXApp loadNibSection:"DocumentPalette.nib" owner:self];
  634.     [[documentPaletteBrowser setAlphabetized:YES] setAbbreviated:YES];
  635.     [documentPaletteBrowser setEditable:YES];
  636.     }
  637.     
  638.     // Load all directories in document path (ignore missing ones).
  639.     [documentPaletteBrowser clear];
  640.     quiet = [Wais isQuiet];
  641.     [Wais setQuiet:YES];
  642.     m = [docPath count];
  643.     for(j=0; j<m; j++) if((docFolder=(const char **)[docPath elementAt:j])
  644.         && (*docFolder) && (docList=[WaisDocument loadFolder:*docFolder]))
  645.     {        
  646.     n = [docList count];
  647.     for(i=0; i<n; i++)
  648.     {
  649.         if([self isDocumentBeingRetrieved:[docList objectAt:i]])
  650.             continue;
  651.         else if([[docList objectAt:i] isRetrieved])
  652.         [documentPaletteBrowser addEntry:[[docList objectAt:i] key]];
  653.     }
  654.     [docList free];
  655.     }
  656.     [Wais setQuiet:quiet];
  657.     [documentPaletteBrowser update];
  658.     [documentPalettePanel orderFront:self];
  659.  
  660.     if(!documentPaletteIWC) documentPaletteIWC =
  661.         [[IconWellControl alloc] initWindow:documentPalettePanel];
  662.     return self;
  663. }
  664.  
  665.  
  666. // ----------------------- DELEGATED FILE OPS ------------------------ //
  667.  
  668.  
  669. - (int)openFile:(const char *)fileName ok:(int *)flag
  670. {
  671.     int rtn;
  672.     
  673.     // Try to open in this application first, then in Workspace.
  674.     if([super handlerForFile:fileName])
  675.     {
  676.     if([super openForHandlerAt:(-1) name:fileName]) *flag = YES;
  677.     else *flag = NO;
  678.     rtn = 0;
  679.     }
  680.     else
  681.     {
  682.     [Wais lockTransaction];
  683.     [[NXApp appSpeaker] setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
  684.     rtn = [[NXApp appSpeaker] openFile:fileName ok:flag];
  685.     [Wais unlockTransaction];
  686.     }
  687.     return rtn;
  688. }
  689.  
  690. - (int)removeFile:(const char *)fileName ok:(int *)flag
  691. {
  692.     int rtn = 0;
  693.     char *auxFile = 0;
  694.     id doc = nil;
  695.     
  696.     // If a WaisDocument, set it as unretrieved
  697.     // and delete the associated ".wais" file too.
  698.  
  699.     if(!fileName) return (-1);
  700.     *flag = NO;
  701.     if(doc = [WaisDocument objectForKey:fileName])
  702.     {
  703.     auxFile = s_malloc(1+strlen(fileName)+strlen(".wais"));
  704.     if(!auxFile) return (-1);
  705.     strcpy(auxFile, fileName); strcat(auxFile, ".wais");
  706.     //!!! Potential thread conflict if doc is being retrieved.
  707.     // (But shouldn't be a problem since such entries are disabled.)
  708.     [doc setUnretrieved];
  709.     }
  710.     [Wais lockFileIO];
  711.     if(doc) rtn = unlink(auxFile);
  712.     if(rtn == 0) rtn = unlink(fileName);
  713.     else unlink(fileName);
  714.     [Wais unlockFileIO];
  715.     if(auxFile) s_free(auxFile);
  716.     if(rtn != 0) return rtn;
  717.     *flag = YES;
  718.     return 0;
  719. }
  720.  
  721. @end
  722.